package ar.edu.itba.it.pdc.httpParser;

import java.util.List;

import ar.edu.itba.it.pdc.httpParser.exceptions.HttpBadRequestException;
import ar.edu.itba.it.pdc.httpParser.exceptions.HttpMethodNotSuportedException;
import ar.edu.itba.it.pdc.httpParser.exceptions.HttpVersionNotSuportedException;
import ar.edu.itba.it.pdc.httpParser.interfaces.Model;
import ar.edu.itba.it.pdc.httpParser.interfaces.Parser;
import ar.edu.itba.it.pdc.httpParser.models.HttpBody;
import ar.edu.itba.it.pdc.httpParser.models.HttpMessage;
import ar.edu.itba.it.pdc.httpParser.models.Token;
import ar.edu.itba.it.pdc.httpParser.models.TokenType;

public abstract class HttpParser implements Parser {
	
	protected enum State {
		UNSTARTED, INITIALLINE, HEADERS, BODY, END, TERMINATED
	};

	protected Lexer lexer;

	protected State state = State.UNSTARTED;

	protected Parser it;
	private HttpHeadersParser hp = new HttpHeadersParser();
	protected Parser bp;

	protected HttpMessage model;

	protected boolean connectionClose;

	private int bytesToRead = -1;

	public boolean parse() throws HttpMethodNotSuportedException,
			HttpBadRequestException, HttpVersionNotSuportedException {
		List<Token> tokens = lexer.tokenise();

		while (!tokens.isEmpty() && bytesToRead != 0 && (bytesToRead >= tokens.get(0).length() || bytesToRead == -1)) {
			bytesToRead = parseToken(tokens.remove(0));
		}
		
		if(!tokens.isEmpty() && bytesToRead < tokens.get(0).length() && bytesToRead > 0){
			int aux = bytesToRead;
			bytesToRead = parseToken(tokens.get(0).firstSubToken(aux));
			tokens.add(0, tokens.get(0).secondSubToken(aux));
		}

		if (bytesToRead == 0) {
			lexer.returnTokens(tokens);
			if (!isTerminated()) {
				Model model1 = bp.getModel();
				model.setBody((HttpBody)model1);
			}
			return true;
		}

		if (bytesToRead == -1) {
			return false;
		}

		Token lastWord = lexer.getLastWord(bytesToRead);

		if (lastWord == null) {
			return false;
		}

		bytesToRead = parseToken(lastWord);

		if (!isTerminated()) {
			model.setBody((HttpBody)bp.getModel());
		}
		return bytesToRead == 0;
	}
	
	public int parseToken(Token token)
			throws HttpMethodNotSuportedException, HttpBadRequestException,
			HttpVersionNotSuportedException {
		int bytesToRead=-1;

		switch (state) {
		case UNSTARTED:

			if (token.isType(TokenType.CRLF)) {
				state = State.INITIALLINE;
			} else if(token.isType(TokenType.CONNECTIONCLOSED)){
				state = State.TERMINATED;
				return 0;
			} else {
				state = State.INITIALLINE;
				parseToken(token);
			}
			break;

		case INITIALLINE:
			if (it.parseToken(token) == 0) {
				model.setInitialLine(it.getModel());
				state = State.HEADERS;
			}
			break;

		case HEADERS:
			if (hp.parseToken(token) == 0) {
				model.setHeaders(hp.getModel());
				connectionClose = model.isConnectionClose();
				bytesToRead = setBodyParser();

			}
			break;

		case BODY:

			if (token.isType(TokenType.CONNECTIONCLOSED)) {
				model.setBody((HttpBody)bp.getModel());
				state = State.END;
				return 0;
			}
			bytesToRead = bp.parseToken(token);
			if (bytesToRead == 0) {
				model.setBody((HttpBody)bp.getModel());
				if (bp.getClass() == ChunkedBodyParser.class) {
					model.removeHeader("Transfer-Encoding");
					model.addHeader("Content-Length", ((ChunkedBodyParser)bp).getBodyLength().toString());
				}
				state = State.END;
				if (connectionClose) {
					return -1;
				} else {
					return 0;
				}
			}
			break;
		case END:
			if (connectionClose && token.isType(TokenType.CONNECTIONCLOSED)) {
				return 0;
			} else {
				throw new HttpBadRequestException(token);
			}
		}
		return bytesToRead;
	}

	@Override
	public HttpMessage getModel() {
		return model;
	}

	public abstract int setBodyParser() throws HttpBadRequestException ;
	
	public boolean isTerminated(){
		return state.equals(State.TERMINATED);
	}

}
